package com.lb_stuff.mcmodify.minecraft;

import com.lb_stuff.mcmodify.location.LocChunkInRegion;
import com.lb_stuff.mcmodify.nbt.Tag;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;

/**
 * Region file reader/writer
 * @see <a href="http://minecraft.gamepedia.com/Region_file_format">Region file format</a> on the Minecraft Wiki
 */
public class FileRegion extends Region
{
	/**
	 * The Region File.
	 */
	private final File rf;

	/**
	 * Constructs this region from a Region File. If the file does not exist it is created with no chunks in it.
	 * @param mca The Region File.
	 * @throws IOException if an error occurs while reading the region file.
	 */
	public FileRegion(File mca) throws IOException
	{
		rf = mca;
		if(!rf.exists())
		{
			rf.createNewFile();
			try(FileOutputStream region = new FileOutputStream(rf))
			{
				region.write(new byte[(int)CHUNK_SECTORS_START]);
			}
		}
	}

	@Override
	public Chunk getChunk(LocChunkInRegion pos) throws IOException
	{
		try(RandomAccessFile region = new RandomAccessFile(rf, "r"))
		{
			region.seek(LOCATIONS_SECTOR_START + chunkIndex(pos)*4);
			LocationPair loc = new LocationPair(region);
			if(loc.offset > 0 && loc.size > 0)
			{
				region.seek(loc.offset);
				int length = region.readInt();
				CompressionScheme compressed = CompressionScheme.fromId(region.readByte());
				byte[] chunk = new byte[length-1];
				region.readFully(chunk);
				try(InputStream is = compressed.getInputStream(new ByteArrayInputStream(chunk)))
				{
					Tag ti = Tag.deserialize(is);
					return new Chunk((Tag.Compound)ti);
				}
			}
		}
		return null;
	}
	public Tag getTag(LocChunkInRegion pos) throws IOException
	{
		try(RandomAccessFile region = new RandomAccessFile(rf, "r"))
		{
			region.seek(LOCATIONS_SECTOR_START + chunkIndex(pos)*4);
			LocationPair loc = new LocationPair(region);
			if(loc.offset > 0 && loc.size > 0)
			{
				region.seek(loc.offset);
				int length = region.readInt();
				CompressionScheme compressed = CompressionScheme.fromId(region.readByte());
				byte[] chunk = new byte[length-1];
				region.readFully(chunk);
				try(InputStream is = compressed.getInputStream(new ByteArrayInputStream(chunk)))
				{
					Tag ti = Tag.deserialize(is);
					return ti;
				}
			}
		}
		return null;
	}
	@Override
	public int getTimestamp(LocChunkInRegion pos) throws IOException
	{
		try(RandomAccessFile region = new RandomAccessFile(rf, "r"))
		{
			region.seek(TIMESTAMPS_SECTOR_START + chunkIndex(pos)*4);
			return region.readInt();
		}
	}

	@Override
	public void setChunk(LocChunkInRegion pos, Chunk c) throws IOException
	{
		try(RandomAccessFile region = new RandomAccessFile(rf, "rw"))
		{
			final int index = chunkIndex(pos);
			//new a chunk
			if(c == null)
			{
				region.seek(LOCATIONS_SECTOR_START + index*4);
				new LocationPair(0, 0).serialize(region);
				return;
			}
			final byte[] chunkdata;
			try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
			{
				baos.write(CompressionScheme.GZip.getId());
				try(OutputStream os = CompressionScheme.GZip.getOutputStream(baos))
				{
					c.ToNBT("").serialize(os);
				}
				chunkdata = baos.toByteArray();
			}

			region.seek(LOCATIONS_SECTOR_START + index*4);
			final long offset = nextSector(region.length());
			long compassedlen = chunkdata.length+4;
			long bl = ((offset/Region.SECTOR_BYTES)<<8) + compassedlen/Region.SECTOR_BYTES + ((compassedlen%Region.SECTOR_BYTES==0)?0:1);
			region.writeInt((int)bl);
			region.seek(offset);
			region.writeInt(chunkdata.length);
			region.write(chunkdata);
			if(compassedlen%Region.SECTOR_BYTES != 0){
				region.write(new byte[(int)(Region.SECTOR_BYTES - (compassedlen%Region.SECTOR_BYTES))]);
			}
		}
	}
	protected static long nextSector(long offset)
	{
		if(offset % SECTOR_BYTES == 0)
		{
			return offset;
		}
		return ((offset/SECTOR_BYTES) + 1)*SECTOR_BYTES;
	}
	@Override
	public void setTimestamp(LocChunkInRegion pos, int timestamp) throws IOException
	{
		try(RandomAccessFile region = new RandomAccessFile(rf, "rw"))
		{
			region.seek(TIMESTAMPS_SECTOR_START + chunkIndex(pos)*4);
			region.writeInt(timestamp);
		}
	}
}